package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Override
    public void addUserAccount(String value) {
        UserAccount userAccount = new UserAccount();

        userAccount.setUserId(Long.parseLong(value));
        userAccount.setTotalAmount(new BigDecimal("0"));
        userAccount.setLockAmount(new BigDecimal("0"));
        userAccount.setAvailableAmount(new BigDecimal("0"));
        userAccount.setTotalIncomeAmount(new BigDecimal("0"));
        userAccount.setTotalPayAmount(new BigDecimal("0"));
        userAccountMapper.insert(userAccount);
    }

    @Override
    public BigDecimal getAvailableAmount() {
        // 构建查询条件对象

        LambdaQueryWrapper<UserAccount> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(UserAccount::getUserId, AuthContextHolder.getUserId());
        UserAccount userAccount = userAccountMapper.selectOne(lambdaQueryWrapper);
        return userAccount.getAvailableAmount();
    }

    @Override
    public AccountLockResultVo checkAndLock(AccountLockVo accountLockVo) {

        // 获取交易号
        String orderNo = accountLockVo.getOrderNo() ;

        // 构建防止重复请求的redisKey，保证接口幂等性
        String idempotentKey = "check:idempotent:" + accountLockVo.getOrderNo() ;

        // 构建检查锁定结果的数据key
        String accountLockResultKey = "account:lock:result:" + accountLockVo.getOrderNo() ;

        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, accountLockVo.getOrderNo(), 1, TimeUnit.HOURS);

        //表示重复提交
        if (!aBoolean){

            String accountLockResultJSON = redisTemplate.opsForValue().get(accountLockResultKey);

            if(!StringUtils.isEmpty(accountLockResultJSON)) {
                return JSON.parseObject(accountLockResultJSON , AccountLockResultVo.class) ;
            }else {   // 还未计算出结果就再次请求
                new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_REPEAT) ;
            }

        }

        // 获取accountLockVo对象的相关参数
        Long userId = accountLockVo.getUserId() ;
        String content = accountLockVo.getContent() ;
        BigDecimal amount = accountLockVo.getAmount() ;
        //	核对账户可用金额并锁定账户数据(悲观锁)；查询返回的是满足要求的账户
       UserAccount userAccount = userAccountMapper.check(accountLockVo.getUserId(),accountLockVo.getAmount());
        if (null == userAccount){
            redisTemplate.delete(idempotentKey) ;			// 删除幂等性key的保证
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS) ;
        }

        //	锁定账户金额
        int lock = userAccountMapper.lock(accountLockVo.getUserId(), accountLockVo.getAmount());
        if(lock == 0) { // 锁定失败
            redisTemplate.delete(idempotentKey) ;			// 删除幂等性key的保证
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR) ;
        }

        //锁定成功	添加账户变更日志数据
        this.log(userId , "锁定:" + content ,
                SystemConstant.ACCOUNT_TRADE_TYPE_LOCK , amount , orderNo);

        // 构建响应结果数据
        AccountLockResultVo accountLockResultVo = new AccountLockResultVo();
        accountLockResultVo.setUserId(userId);
        accountLockResultVo.setAmount(amount);
        accountLockResultVo.setContent(content);
        redisTemplate.opsForValue().set(accountLockResultKey , JSON.toJSONString(accountLockResultVo) , 1 , TimeUnit.HOURS);

        // 返回
        return accountLockResultVo;
    }

    @Override
    public void minusAccount(String value) {
        // 保证接口的幂等性
        String idempotentKey = "account:minus:idempotent:" + value ;
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, value, 5, TimeUnit.MINUTES);
        if(!aBoolean) return ;

        // 进行账户金额的扣减
        // 根据订单的编号从Redis中获取账户余额锁定结果数据
        String lockResultRedisKey = "account:lock:result:" + value ;
        String json = redisTemplate.opsForValue().get(lockResultRedisKey);
        if(StringUtils.isEmpty(json))  return ;
        AccountLockResultVo accountLockResultVo = JSON.parseObject(json, AccountLockResultVo.class);
        int count = userAccountMapper.minusAccount(accountLockResultVo.getUserId() , accountLockResultVo.getAmount())  ;
        if(count == 0) {
            redisTemplate.delete(idempotentKey) ;
            throw new GuiguException(ResultCodeEnum.ACCOUNT_MINUSLOCK_ERROR);
        }

        // 记录账户变更的日志数据
        this.log(accountLockResultVo.getUserId() , "解锁: " + accountLockResultVo.getContent() ,
                SystemConstant.ACCOUNT_TRADE_TYPE_MINUS , accountLockResultVo.getAmount() , value);

        // 从Redis中删除锁定账户金额的结果数据
        redisTemplate.delete(lockResultRedisKey) ;

    }

    @Override
    public void unLockAccount(String value) {
        // 保证接口的幂等性
        String idempotentKey = "account:unlock:idempotent:" + value ;
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(idempotentKey, value, 5, TimeUnit.MINUTES);
        if(!aBoolean) return ;

        // 进行锁定金额的解锁
        String lockResultRedisKey = "account:lock:result:" + value ;
        String json = redisTemplate.opsForValue().get(lockResultRedisKey);
        if(StringUtils.isEmpty(json)) return ;
        AccountLockResultVo accountLockResultVo = JSON.parseObject(json, AccountLockResultVo.class);
        Long userId = accountLockResultVo.getUserId();
        BigDecimal amount = accountLockResultVo.getAmount();
        int count = userAccountMapper.unLockAccount(userId , amount) ;
        if(count == 0) {
            redisTemplate.delete(idempotentKey) ;
            throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
        }

        // 记录日志数据
        this.log(userId , "解锁: " + accountLockResultVo.getContent() ,
                SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK , amount , value);


        // 从Redis中删除锁定账户金额的结果数据
        redisTemplate.delete(lockResultRedisKey) ;
    }


    //添加账户变更日志数据
    private void log(Long userId , String title, String tradeType, BigDecimal amount , String orderNo) {
        UserAccountDetail userAccountDetail = new UserAccountDetail() ;
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        userAccountDetailMapper.insert(userAccountDetail) ;
    }
}
